﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Reflection;
using FoundationLib.Ifc.Attributes;

namespace FoundationLib.Common
{
	/// <summary>
	/// Provides functionality for enumeration types
	/// </summary>
	public static class EnumHelper
	{
		/// <summary>
		/// Returns the display name denoted by <see cref="EnumDisplayNameAttribute"/> attributes from the 
		/// given enumeration type.
		/// </summary>
		/// <remarks>
		/// If no display name attribute exists for the given field value, this method returns the name(s) of the
		/// field constant(s) analogue to <see cref="Enum.GetName(Type, object)"/> or 
		/// <see cref="Enum.Format(Type, object, string)"/> methods, respectively.
		/// </remarks>
		/// <param name="enumType">The enumeration type</param>
		/// <param name="value">The enumeration field value</param>
		/// <returns>The enumeration field display name</returns>
		public static string GetDisplayName(Type enumType, object value)
		{
			return (GetDisplayName(enumType, null, value));
		}

		/// <summary>
		/// Returns the display name denoted by <see cref="EnumDisplayNameAttribute"/> attributes from the 
		/// given enumeration type, taking the specified category name into account.
		/// </summary>
		/// <remarks>
		/// If no display name attribute exists for the given field value, this method returns the name(s) of the
		/// field constant(s) analogue to <see cref="Enum.GetName(Type, object)"/> or 
		/// <see cref="Enum.Format(Type, object, string)"/> methods, respectively.
		/// </remarks>
		/// <param name="enumType">The enumeration type</param>
		/// <param name="category">The display name category</param>
		/// <param name="value">The enumeration field value</param>
		/// <returns>The enumeration field display name</returns>
		public static string GetDisplayName(Type enumType, string category, object value)
		{
			Contract.Requires(enumType != null);
			Contract.Requires(value != null);
			Contract.Requires(enumType.IsEnum, "The given type is not an enumeration type.");

			FlagsAttribute attribute = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).FirstOrDefault() as FlagsAttribute;
			List<string> fields = new List<string>();
			if (attribute == null)
			{
				string name = Enum.GetName(enumType, value);
				fields.Add(name);
			}
			else
			{
				string names = Enum.Format(enumType, value, "g");
				fields.AddRange(names.Split(','));
			}

			List<string> displayNames = new List<string>();
			foreach (string field in fields)
			{
				string selectedField = field.Trim();
				FieldInfo member = enumType.GetField(selectedField, BindingFlags.Static | BindingFlags.Public);

				if (member == null)
					continue;

				EnumDisplayNameAttribute[] attributes = member.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false)
					.Cast<EnumDisplayNameAttribute>().ToArray();

				if (attributes.Length == 0)
				{
					displayNames.Add(selectedField);
				}
				else
				{
					string displayName = null;
					if (category != null)
					{
						displayName = attributes.Where(x => x.Category == category).Select(x => x.DisplayName).FirstOrDefault();
					}
					else
					{
						displayName = attributes.Select(x => x.DisplayName).FirstOrDefault();	
					}

					if (displayName == null)
						displayNames.Add(selectedField);
					else
						displayNames.Add(displayName);
				}
			}

			return String.Join(", ", displayNames);
		}

		/// <summary>
		/// Returns all constant names defined in the enumeration of the given type, taking 
		/// <see cref="EnumDisplayNameAttribute"/> attributes into account.
		/// </summary>
		/// <remarks>
		/// This method takes the real constant names in the enumeration as fallback values.
		/// </remarks>
		/// <param name="enumType">The enumeration type</param>
		/// <param name="category">The display name category</param>
		/// <returns>An array of display names representing the contants defined in the given enumeration</returns>
		public static string[] GetDisplayNames(Type enumType, string category)
		{
			Contract.Requires(enumType != null);
			Contract.Requires(enumType.IsEnum, "The given type is not an enumeration type.");

			string[] names = Enum.GetNames(enumType);

			List<string> result = new List<string>();
			foreach (string name in names)
			{		
				FieldInfo fieldInfo = enumType.GetField(name, BindingFlags.Static | BindingFlags.Public);
				if (fieldInfo == null)
				{
					result.Add(name);
					continue;
				}

				EnumDisplayNameAttribute[] attributes = fieldInfo.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false)
					.Cast<EnumDisplayNameAttribute>().ToArray();

				if (attributes.Length == 0)
				{
					result.Add(name);
					continue;
				}
				else
				{
					string displayName = null;
					if (!String.IsNullOrWhiteSpace(category))
						displayName = attributes.Where(x => x.Category == category).Select(x => x.DisplayName).FirstOrDefault();
					else
						displayName = attributes.Select(x => x.DisplayName).FirstOrDefault();

					if (displayName == null)
						displayName = name;

					result.Add(displayName);
				}
			}

			return result.ToArray();
		}

		/// <summary>
		/// Returns all constant names defined in the enumeration of the given type, taking 
		/// <see cref="EnumDisplayNameAttribute"/> attributes into account.
		/// </summary>
		/// <remarks>
		/// This method takes the real constant names in the enumeration as fallback values.
		/// </remarks>
		/// <param name="enumType">The enumeration type</param>
		/// <returns>An array of display names representing the contants defined in the given enumeration</returns>
		public static string[] GetDisplayNames(Type enumType)
		{
			return GetDisplayNames(enumType, null);
		}
	}
}
